package com.weic.easycache.file;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import android.content.Context;
import android.util.Log;

import com.weic.easycache.L;
import com.weic.easycache.exception.FileCreateFailureException;
import com.weic.easycache.exception.SdCardNotMountedException;
import com.weic.easycache.exception.SdCardNotValidException;
import com.weic.easycache.phone.Storage.SdCard;

/**
 * 注意需要权限android.permission.WRITE_EXTERNAL_STORAGE，该权限包含了
 * android.permission.READ_EXTERNAL_STORAGE
 * @author WeiChou
 */
public class FileUtils {
	private static String LOG_TAG = "FileUtils";

	/**
	 * String str = "/data/data/12///34\\\\///\\\\data/78";
	 * 注意\\\\表示正则表达式字符串"\\"，用于匹配转义之前的字符串"\\"，而"\\"表示一个“\”字符，
	 * 经测试必须成四个反斜杠的倍数出现才能正确匹配多个“\”字符、
	 * String[] arr0 = dirName.split("[/\\\\]+");
	 */
	public static final String separatorRegExp = "[\\s]*[/\\\\]+[\\s/\\\\]*";
	public static final String fileNameRegExp = "^[\\w%+,.=-][\\w %+,.=-]*[\\w%+,.=-]$|[\\w%+,.=-]";	//一个非点字符，或者点开头非点结尾的字符串（注意正则里面-+不能在一起，可测试）

	public static final Pattern separatorPattern = Pattern.compile(separatorRegExp);
	public static final Pattern fileNamePattern = Pattern.compile(fileNameRegExp);

	public static boolean isFileNameValid(String name) {
		return fileNamePattern.matcher(name).matches();
	}

	/**如果不合法将抛异常**/
	public static void checkFileNameValid(String name) {
		if(!isFileNameValid(name)) throw new IllegalArgumentException("文件名不合法：" + name);
	}

	public static String adjustPathSeparator(String path) {
		return separatorPattern.matcher(path).replaceAll(File.separator);
	}

	public static String[] getPathElements(String path) {
		return getPathElementsSepAdjusted(adjustPathSeparator(path));
	}

	private static String[] getPathElementsSepAdjusted(String path) {
		String[] elements = path.split(File.separator);
		int offset = 0;
		for(String s : elements) {
			if(s != null && s.length() > 0) {	//一般在路径的第一个字符为斜杠的情况下会在第[0]个元素产生一个""
				checkFileNameValid(s);
				elements[offset++] = s;
			}
		}
		String[] dirs = new String[offset];
		System.arraycopy(elements, 0, dirs, 0, offset);
		return dirs;
	}

	public static String formatPath(String path) {
		String pathSepAdjusted = adjustPathSeparator(path);
		return formatPathWithPathElementsAndSepAdjusted(getPathElementsSepAdjusted(pathSepAdjusted), pathSepAdjusted);
	}

	//不能开放这个，仅限Api内部使用
	private static String formatPathWithPathElementsAndSepAdjusted(String[] dirs, String pathSepAdjusted) {
		String path = pathSepAdjusted;
		boolean startp = path.startsWith(File.separator);
		boolean endp = path.endsWith(File.separator);
		path = startp ? File.separator : "";
		path += formatPathWithPathElements(dirs);
		path += endp ? File.separator : "";
		return path;
	}

	//不能开放这个，仅限Api内部使用
	private static String formatPathWithPathElements(String[] dirs) {
		String path = "";
		boolean first = true;
		for(String dir : dirs) {
			if(first) {
				first = false;
			} else {
				path += File.separator;
			}
			path += dir;
		}
		return path;
	}

	/**
	 * 根据指定的路径创建文件
	 * @param filePath			文件路径
	 * @param deleteMissType	如果路径中已存在的节点（中间目录或文件）类型与需求不符是否删除
	 * @return					返回创建成功的文件或抛出异常
	 * @throws FileCreateFailureException
	 */
	public static File makeFile(String filePath, boolean deleteMissType) throws FileCreateFailureException {
		String pathSepAdjusted = adjustPathSeparator(filePath);
		String[] elements = getPathElementsSepAdjusted(pathSepAdjusted);
		if(elements == null || elements.length == 0) throw new FileCreateFailureException("路径为空。filePath: " + filePath + ", formatPath：" + formatPathWithPathElementsAndSepAdjusted(elements, pathSepAdjusted));
		File file;
		if(elements.length == 1) {
			file = new File(elements[0]);
		} else {
			String[] dirs = new String[elements.length - 1];
			System.arraycopy(elements, 0, dirs, 0, dirs.length);
			File dir = makeDir(dirs, pathSepAdjusted.startsWith(File.separator), deleteMissType);
			file = new File(dir, elements[elements.length - 1]);
		}
		if(file.exists()) {
			if(file.isFile()) {
				return file;
			} else {
				if(deleteMissType) {
					deleteFile(file, null, true);
					//下面进行了创建
				} else 
					throw new FileCreateFailureException("要创建的文件是个已存在的目录：" + file.getPath());
			}
		}
		try {
			file.createNewFile();
		} catch(IOException e) {
			throw new FileCreateFailureException(e);
		}
		if(!file.exists()) throw new FileCreateFailureException();
		return file;
	}

	private static File makeDir(String[] dirs, boolean startSep, boolean deleteMissType) throws FileCreateFailureException {
		File dir = null;
		String path = startSep ? File.separator : "";
		path += formatPathWithPathElements(dirs);
		dir = new File(path);
		if(!dir.exists()) dir.mkdirs();
		if(dir.isDirectory()) return dir;

		//dir.mkdirs();创建失败或者路径中有存在的文件而不是目录
		dir = null;
		for(int i=0; i < dirs.length; i++) {
			if(dir == null) {
				dir = new File((startSep ? File.separator : "") + dirs[i]);
			} else {
				dir = new File(dir, dirs[i]);
			}
			if(dir.exists()) {
				if(dir.isDirectory()) {
					continue;
				} else {
					if(deleteMissType) {
						deleteFile(dir, null, true);
						//下面进行了创建
					} else 
						throw new FileCreateFailureException("要创建的目录是个已存在的文件：" + dir.getPath());
				}
			}
			dir.mkdir();
			if(!dir.exists()) throw new FileCreateFailureException("目录创建失败：" + dir.getPath());
		}
		return dir;
	}

	public static File makeDir(String dirPath, boolean deleteMissType) throws FileCreateFailureException {
		String pathSepAdjusted = adjustPathSeparator(dirPath);
		String[] elements = getPathElementsSepAdjusted(pathSepAdjusted);
		if(elements == null || elements.length == 0) throw new FileCreateFailureException("路径为空。dirPath: " + dirPath + ", formatPath：" + formatPathWithPathElementsAndSepAdjusted(elements, pathSepAdjusted));
		return makeDir(elements, pathSepAdjusted.startsWith(File.separator), deleteMissType);
	}

	public static File createAppDir(Context context, SdCard sdcard) throws SdCardNotMountedException, SdCardNotValidException, FileCreateFailureException {
		File file = null;
		if(sdcard.isMounted(context)) {
			if(sdcard.isValid(context)) {
				file = makeDir(sdcard.getAppDataDir(context), true);
			} else {
				L.i(LOG_TAG, "getBaseDir()--存储卡无效，可能没有格式化");
				throw new SdCardNotValidException("存储卡无效，可能没有格式化");
			}
		} else {
			L.i(LOG_TAG, "getBaseDir()--存储卡没有挂载");
			throw new SdCardNotMountedException("存储卡没有挂载");
		}
		return file;
	}

	public static boolean isExistsFile(String path) {
		return isExistsFile(new File(path)) || isExistsFile(new File(formatPath(path)));
	}

	public static boolean isExistsFile(File file) {
		return file.exists() && file.isFile();
	}

	public static boolean isExistsDir(String dir) {
		return isExistsDir(new File(dir)) || isExistsDir(new File(formatPath(dir)));
	}

	public static boolean isExistsDir(File file) {
		return file.exists() && file.isDirectory();
	}

	/**
	 * 删除文件或者目录
	 * @param fileDelete		要删除的文件或目录
	 * @param suffix			要删除的文件后缀，如果为空则删除全部
	 * @param deleteRootDir		是否删除当前要删除的目录树的根目录（有时只想清空目录，而不删除该目录）
	 */
	public static void deleteFile(File fileDelete, String suffix, boolean deleteRootDir) {
		File current = fileDelete;
		if(current == null || !current.exists()) return;
		if(current.isFile()) {	//表明要删除的是文件，就直接删除，忽略后缀
			current.delete();
		} else {
			File[] files = current.listFiles();
			for(File file : files) {
				if(file.isDirectory()) {
					deleteFile(file, suffix, true);	//注意参数true，表示子目录的根目录都删除
					L.i(LOG_TAG, "[delete]canWrite:" + file.canWrite() + ", path:" + file.getPath());
				} else {
					L.i(LOG_TAG, "[delete]canWrite:" + file.canWrite() + ", path:" + file.getPath());
					if(suffix == null || suffix.trim().length() <= 0
							|| file.getName().toLowerCase().endsWith(suffix.trim())) file.delete();
				}
			}
			if(deleteRootDir) current.delete();
		}
	}

	public static void printFileList(File dirFile) {
		File current = dirFile;	//返回的必定是目录
		if(current == null || !current.exists()) return;
		if(current.isFile()) {
			Log.i(LOG_TAG, "[printFileList][file]path:" + current.getPath());
		} else {
			File[] files = current.listFiles();
			for(File file : files) {
				if(file.isDirectory()) {
					Log.i(LOG_TAG, "[printFileList][dir]path:" + file.getPath());
					printFileList(file);
				} else {
					Log.i(LOG_TAG, "[printFileList][file]path:" + file.getPath());
				}
			}
		}
	}

	/**
	 * 对写文件进行了简单版本控制的工具类。
	 * 解决进程间共享SharedPreferences时值丢失的问题。
	 **/
	public static class VersionedFile {
		private static String CHARSET = "utf-8";

		public static synchronized void saveAsFile(Context context, String fileName, String content) {
			if (content == null) {
				deleteAll(getVersionFiles(context, fileName, false));
			} else {
				VersionFiles vfiles = getVersionFiles(context, fileName, true);
				FileOutputStream out = null;
				try {
					out = new FileOutputStream(vfiles.writing);
					out.write(content.getBytes(CHARSET));
					vfiles.writing.renameTo(vfiles.renameTo);
				} catch (FileNotFoundException e) {
					L.e(VersionedFile.class, e);
					throw new RuntimeException(e);
				} catch (IOException e) {
					L.e(VersionedFile.class, e);
				} finally {
					if (out != null) try { out.close(); } catch (IOException e) {}
				}
				deleteOthers(vfiles.others);
			}
		}

		public static synchronized String getFromFile(Context context, String fileName) {
			VersionFiles vfiles = getVersionFiles(context, fileName, false);
			if (vfiles.read != null) {
				FileInputStream in = null;
				ByteArrayOutputStream arrayBuffer = new ByteArrayOutputStream(512);
				try {
					in = new FileInputStream(vfiles.read);
					byte[] buffer = new byte[512];
					int count;
					while ((count = in.read(buffer)) != -1) {
						arrayBuffer.write(buffer, 0, count);
					}
					return new String(arrayBuffer.toByteArray(), CHARSET);
				} catch (FileNotFoundException e) {
					L.e(VersionedFile.class, e);
					throw new RuntimeException(e);
				} catch (IOException e) {
					L.e(VersionedFile.class, e);
				} finally {
					if (in != null) try { in.close(); } catch (IOException e) {}
					if (arrayBuffer != null) try { arrayBuffer.close(); } catch (IOException e) {}
				}
			}
			return null;
		}

		private static File getFileSharedPrefDir(Context context) {
			return new File(context.getFilesDir(), "fileSharedPrefs");
		}

		private static void deleteAll(VersionFiles vfiles) {
			if (vfiles.writing != null) vfiles.writing.delete();
			if (vfiles.renameTo != null) vfiles.renameTo.delete();
			if (vfiles.read != null) vfiles.read.delete();
			deleteOthers(vfiles.others);
		}

		private static void deleteOthers(File[] others) {
			if (others != null && others.length > 0) {
				for (File file : others) {
					file.delete();
				}
			}
		}

		private static VersionFiles getVersionFiles(Context context, String fileName, boolean isWrite) {
			FileUtils.checkFileNameValid(fileName);
			String fileName_ = fileName + "-";
			File fileSharedPrefDir = getFileSharedPrefDir(context);
			String[] names = fileSharedPrefDir.list();
			String maxName = null;
			VersionFiles vfiles = new VersionFiles();
			if (names != null && names.length > 0) {
				List<File> list = new ArrayList<File>();
				String fileName_1 = fileName_ + "1";
				int len = fileName.length();
				for (String name : names) {
					if (name.startsWith(fileName_1) && name.lastIndexOf("-1") == len) {
						if (maxName == null) {
							maxName = name;
						} else if (name.compareToIgnoreCase(maxName) > 0) {
							list.add(new File(fileSharedPrefDir, maxName));
							maxName = name;
						} else {
							list.add(new File(fileSharedPrefDir, name));
						}
					}
				}
				//无论读写模式都需要
				if (list.size() > 0) vfiles.others = list.toArray(new File[list.size()]);
			}
			int fileNameIndex = 1000000000;
			if (isWrite) {
				if (maxName != null) {
					try {
						int fileNameIndex0 = Integer.valueOf(maxName.substring(fileName_.length()));
						if (fileNameIndex0 >= fileNameIndex) {
							fileNameIndex = fileNameIndex0;
							fileNameIndex++;
						}
					} catch (Exception e) {}
				}
				vfiles.writing = new File(fileSharedPrefDir, fileName + "." + fileNameIndex);
				vfiles.renameTo = new File(fileSharedPrefDir, fileName_ + fileNameIndex);
				try {
					FileUtils.makeFile(vfiles.writing.getPath(), true);
				} catch (FileCreateFailureException e) {
					L.e(VersionedFile.class, e);
					throw new RuntimeException(e);
				}
			} else {
				vfiles.read = maxName != null ? new File(fileSharedPrefDir, maxName) : null;
			}
			return vfiles;
		}

		private static class VersionFiles {
			public File writing;
			public File renameTo;
			public File read;
			public File[] others;
		}
	}
}
